package sim_moteur;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Stack;

import sim_moteur.element.Chose;
import sim_moteur.element.Vivant;
import sim_moteur.element.Vivant.Action;
import sim_moteur.element.Vivant.Etat;
import sim_moteur.element.animeaux.Animal;
import sim_moteur.element.vegetal.Vegetal;
import sim_moteur.factory.SimFactory;
import util.SequencePredeterminee;
import util.SimProperties;

/**
 * Classe principale. Gère une simulation avec des animeaux des vegetaux et des
 * choses inertes.
 * 
 * @author Martin Simoneau
 * @version 1.0
 * 
 */
public class Simulateur {

	private static final String SIMULATION_GENERATION_SPONTANEE_ACTIF = "simulation.generation-spontanee.actif";
	public final static String ANIMAL_NAME = "Animal_";
	public final static String VEGETAL_NAME = "Vegetal_";
	public final static String CHOSE_NAME = "Chose";

	public final static String SUFFIX_VITALITE = ".initial.vie";
	public final static float VITALITE_ANIMAL_MIN = 5.0f;
	public final static float VITALITE_ANIMAL_MAX = 15.0f;
	public final static float POURCENTAGE_DEGENERATION_SPONTANEE = 0.03f; // 3%
																			// à
																			// chaque
																			// pas
																			// de
																			// temps

	private Map<String, Chose> choses;
	private Map<String, Animal> animaux;
	private Map<String, Vegetal> vegetaux;

	private Map<String, Animal> animauxMorts;
	private Map<String, Vegetal> vegetauxMorts;

	private int compteurChoses;
	private int compteurAnimaux;
	private static int compteurVegetaux;

	private long step;

	public long getStep() {
		return step;
	}

	public void setStep(long step) {
		this.step = step;
	}

	/**
	 * 
	 * @author Martin Simoneau
	 * 
	 */
	public enum ChosesPossibles {
		CHOSE, ANIMAL, VEGETAL
	};

	public Simulateur() {
		animaux = new HashMap<String, Animal>();
		animauxMorts = new HashMap<String, Animal>();
		vegetauxMorts = new HashMap<String, Vegetal>();
		choses = new HashMap<String, Chose>();
		vegetaux = new HashMap<String, Vegetal>();
		compteurChoses = 0;
		compteurAnimaux = 0;
		compteurVegetaux = 0;
		step = 0;
	}

	public void init() {
		// Il faut absolument réinitialiser les objets static
		SimProperties.init();
		Terrain.init();// terrain dépend de SimProperties
		SequencePredeterminee.reset();
		animaux.clear();
		animauxMorts.clear();
		vegetauxMorts.clear();
		choses.clear();
		vegetaux.clear();
		compteurChoses = 0;
		compteurAnimaux = 0;
		compteurVegetaux = 0;
		step = 0;
	}

	/**
	 * Lance la simulation. Ne fonctionne pas sans threads.
	 */
	public void simule(long nombreDeStep) {
		step = 0;
		for (long i = nombreDeStep; i != 0 && resteToujoursUnVivant(); i--) {
			step();

		}
	}

	/**
	 * Fait avancer la simulation d'un pas de temps.
	 */
	@SuppressWarnings("unchecked")
	public void step() {
		Iterator<Vivant> iterator = getVivants().values().iterator();
		while (iterator.hasNext()) {
			Vivant etre = iterator.next();
			if (!etre.vit(step)) {
				etre.meurt();
				// iterator.remove();
			}
		}

		// On reproduit les plantes matures
		Iterator<Vegetal> vegetauxIterator = getVegetaux().values().iterator();
		while (vegetauxIterator.hasNext()) {
			Vegetal plante = vegetauxIterator.next();
			if (plante.getAction() == Action.VEGETAL_REPRODUIRE) {
				gereReproductionDe(plante);
				if (!plante.estVivant()) {
					plante.meurt();
					// vegetauxIterator.remove();
					vegetauxMorts.put(plante.getName(), plante);
				}
			}
		}
		// On ajoute des animaux à l'occasion parce qu'il n'y a pas de
		// reproduction
		if (generationSpontanee()) {
			// ajouteChoseGenerique(ChosesPossibles.ANIMAL, 1);
			Animal animal = (Animal) SimFactory.getInstance()
					.getFactory(ChosesPossibles.ANIMAL).get();
			ajouteAnimaux((Map<String, Animal>) new HashMap<String, Animal>()
					.put(animal.getName(), animal));
		}
		step++;
	}

	/**
	 * Gère la reproduction des vegetaux. Lorsqu'il atteint la maturité un
	 * végétal à un certain pourcentage de chance de se reproduire. Cette
	 * méthode ajoute le végétal enfant.
	 * 
	 * @param plante
	 *            Le vegetal qui essaie de se reproduire
	 */
	private Vegetal gereReproductionDe(Vegetal plante) {
		Vegetal retVegetal = null;
		Terrain terrain = Terrain.getInstance();

		// On crée la nouvelle plante.
		Vegetal vegetal = new Vegetal("Vegetal_" + compteurVegetaux++);
		Coordonnee nouvelleCoordonnee = terrain.trouveUnePlacePresDe(plante);
		// Il y a une place pour la nouvelle plante
		if (nouvelleCoordonnee != null
				&& !nouvelleCoordonnee.equals(vegetal.getCoordonnees())) {
			// On place le noueau vegetal sur le terrain
			terrain.place(vegetal, nouvelleCoordonnee);

			// On ajoute le vegetal dans notre liste de végétaux
			vegetaux.put(vegetal.getName(), vegetal);
		}

		// Le parent est mort suite à la reproduction
		return retVegetal;

	}

	/**
	 * CÉvalue s'il y a génération spontanée. Pouf! magie!
	 * 
	 * @return vrai s'il ya génération spontanée
	 */
	private static boolean generationSpontanee() {
		boolean generationActive = SimProperties.getInstance()
				.getBooleanProperty(SIMULATION_GENERATION_SPONTANEE_ACTIF);
		return generationActive
				&& (Math.random() + POURCENTAGE_DEGENERATION_SPONTANEE) > 1;
	}

	/**
	 * 
	 */
	public void enleveMort() {
		Iterator<Vivant> iterator = getVivants().values().iterator();
		while (iterator.hasNext()) {
			Vivant etre = iterator.next();
			if (etre.getEtat() == Etat.MORT) {
				if (etre instanceof Animal) {
					animauxMorts.put(etre.getName() + "_S" + step,
							(Animal) etre);
					animaux.remove(etre.getName());
					Terrain.getInstance().place(
							new Chose(CHOSE_NAME + etre.getName() + "mort"),
							etre.getCoordonneesPrecedentes());
				} else if (etre instanceof Vegetal) {
					vegetauxMorts.put(etre.getName() + "_S" + step,
							(Vegetal) etre);
					vegetaux.remove(etre.getName());

				}
				Terrain.getInstance().retireChose(etre);
				iterator.remove();
			}
		}
	}

	/**
	 * Indique s'il reste au moins un être vivant
	 * 
	 * @return vrai s'il reste au moins un vivanté
	 */
	public boolean resteToujoursUnVivant() {
		int nombre = 0;
		for (Vivant etre : animaux.values()) {
			if (etre.estVivant()) {
				nombre++;
			}
		}
		return (nombre < 4 && nombre > 0);
	}

	/**
	 * 
	 */
	// public void ajouteChoseGenerique(ChosesPossibles sorte, int nombre) {
	// Terrain terrain = Terrain.getInstance();
	// Chose chose;
	// SimProperties properties = SimProperties.getInstance();
	// for (int i = 0; i < nombre; i++) {
	//
	// switch (sorte) {
	//
	// case ANIMAL:
	// String name = ANIMAL_NAME + compteurAnimaux++;
	// float vitalite = properties.getFloatProperty(name
	// + SUFFIX_VITALITE);
	// if (vitalite < 0)
	// vitalite = (int) ((Math.random() * VITALITE_ANIMAL_MAX -
	// VITALITE_ANIMAL_MIN) + VITALITE_ANIMAL_MIN);
	// chose = new Animal(name, vitalite);
	// animaux.put(chose.getName(), (Animal) chose);
	// break;
	// case VEGETAL:
	// chose = new Vegetal("Vegetal_" + compteurVegetaux++,
	// Vegetal.VIE_DEPART);
	// vegetaux.put(chose.getName(), (Vegetal) chose);
	// break;
	// default:// chose générique
	// chose = new Chose("Chose_" + compteurChoses++);
	// choses.put(chose.getName(), chose);
	// break;
	// }
	//
	// terrain.trouveUnePlace(chose);
	// }
	// }

	/**
	 * Ajoute les animaux dans la simulation
	 * 
	 * @param animaux
	 *            les animaux à ajouter
	 */
	public void ajouteAnimaux(Map<String, Animal> animaux) {
		this.animaux.putAll(animaux);
		compteurAnimaux += animaux.size();
	}

	/**
	 * Ajoute les choses dans la simulation
	 * 
	 * @param choses
	 *            les choses à ajouter
	 */
	public void ajouteChoses(Map<String, Chose> choses) {
		this.choses.putAll(choses);
		compteurAnimaux += choses.size();
	}

	/**
	 * Ajoute les choses dans la simulation
	 * 
	 * @param choses
	 *            les choses à ajouter
	 */
	public void ajouteVegetaux(Map<String, Vegetal> vegetaux) {
		this.vegetaux.putAll(vegetaux);
		compteurAnimaux += vegetaux.size();
	}

	public Map<String, Animal> getAnimaux() {
		return new HashMap<>(animaux);
	}

	public Map<String, Vegetal> getVegetaux() {
		return new HashMap<>(vegetaux);
	}

	/**
	 * Retourne tous les être vivants, plantes et animaux
	 * 
	 * @return une carte contenant les vivants (la cle est son nom)
	 */
	public Map<String, Vivant> getVivants() {
		Map<String, Vivant> retVivants = new HashMap<>();
		retVivants.putAll(animaux);
		retVivants.putAll(vegetaux);

		return retVivants;
	}

	/**
	 * Retourne tous les éléments connus par le simulateur (choses, anim).
	 * 
	 * @return une carte contenant tous les éléments (la cle est son nom)
	 */
	public Map<String, Chose> getToutesLesChoses() {
		Map<String, Chose> retChoses = new HashMap<>();
		retChoses.putAll(animaux);
		retChoses.putAll(vegetaux);
		retChoses.putAll(choses);

		return retChoses;
	}

	/**
	 * Retourne toutes les choses de bases (pas les vivants)
	 * 
	 * @return Une carte contenant les choses.
	 */
	public Map<String, Chose> getLesChosesBasiques() {
		return choses;
	}

	/**
	 * Retourne la chose dont le nom est fournien paramètre. Retourne null si la
	 * chose n'est pas trouvée.
	 * 
	 * @param name
	 *            le nom de la chose recherchée
	 * @return La chose recherchée.
	 */
	public Chose getChose(String name) {
		// On doit retourner toutes les choses dans le simulateur
		Chose retChose = null;
		if (name != null) {
			Map<String, Chose> toutesChoses = new HashMap<>();
			toutesChoses.putAll(choses);
			toutesChoses.putAll(animaux);
			toutesChoses.putAll(vegetaux);
			if (toutesChoses.containsKey(name)) {
				retChose = toutesChoses.get(name);
			}
		}
		return retChose;
	}

	/**
	 * Retourne le nom de la chose située à l'index fournit. L'index est la
	 * position de l'objet dans un tableau qui contiendrait toutes les rangées
	 * bout à bout.
	 * 
	 * @param index
	 *            l'index de l'objet recherchee
	 * @return le nom de la chose
	 */
	public String getNameForIndex(int index) {
		// On convertit l'index en coordonnée
		Coordonnee coordonnee = Terrain.convert(index);

		String retVal = null;
		Stack<Chose> stack = Terrain.getInstance().get(coordonnee.getX(),
				coordonnee.getY());
		if (!stack.empty()) {
			retVal = stack.peek().getName();
		}
		return retVal;
	}

	public static int getNombreDeVegetaux() {
		return compteurVegetaux;
	}

	@Override
	public String toString() {
		String retVal = "";
		Terrain terrain = Terrain.getInstance();
		int largeur = terrain.getXSize();
		int hauteur = terrain.getYSize();
		Chose chose;

		// On affiche affiche un tableau avec la première lettre de chaque type
		// d'élément
		for (int y = 0; y < hauteur; y++) {
			for (int x = 0; x < largeur; x++) {
				Stack<Chose> stack = terrain.get(x, y);
				if (!stack.empty()) {
					chose = terrain.get(x, y).peek();
					retVal += chose.getName().charAt(0);
				} else {
					// S pour sol (absence de chose)
					retVal += "s";
				}
			}
			retVal += "\n";
		}

		return retVal;
	}
}
